package main

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/anacrolix/torrent"
)

func formatFileSize(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// ensureBTClient initialises the BT client if needed
func (m *Manager) ensureBTClient(saveDir string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.btClient != nil {
		return nil
	}
	cfg := torrent.NewDefaultClientConfig()
	// storage root directory
	if saveDir == "" {
		saveDir = m.defaultSavePath
		if saveDir == "" {
			saveDir = defaultDownloadDir()
		}
	}
	_ = os.MkdirAll(saveDir, 0755)
	cfg.DataDir = saveDir
	if m.btListenPort > 0 {
		cfg.ListenPort = m.btListenPort
	}
	// Note: proxy for BT is non-trivial; we skip here for first version.
	c, err := torrent.NewClient(cfg)
	if err != nil {
		return err
	}
	m.btClient = c
	if m.btTorrents == nil {
		m.btTorrents = make(map[string]*torrent.Torrent)
	}
	return nil
}

func (m *Manager) startBT(id string) error {
	m.mu.Lock()
	t, ok := m.tasks[id]
	if !ok {
		m.mu.Unlock()
		return errors.New("task not found")
	}
	t.AddLog("info", "开始处理BT下载任务")
	// prepare save directory
	dir := m.defaultSavePath
	if dir == "" {
		dir = defaultDownloadDir()
	}
	_ = os.MkdirAll(dir, 0755)
	// set a temporary save path as directory; final will be updated after got info
	if t.SavePath == "" {
		// For BT, SavePath points to directory/target (final file or folder). We'll update after got info.
		t.SavePath = dir
	}
	// concurrent guard
	if m.workers == nil {
		m.workers = make(map[string]*downloadWorker)
	}
	if _, exists := m.workers[id]; exists {
		m.mu.Unlock()
		return nil
	}
	// mark status
	t.Status = StatusDownloading
	m.mu.Unlock()
	m.emitUpdate()

	if err := m.ensureBTClient(dir); err != nil {
		m.failTask(id, fmt.Errorf("bt init: %w", err))
		return nil
	}

	ctx, cancel := context.WithCancel(context.Background())
	w := &downloadWorker{ctx: ctx, cancel: cancel}
	w.wg.Add(1)
	m.mu.Lock()
	m.workers[id] = w
	m.mu.Unlock()
	t.AddLog("info", "开始下载种子元数据")

	go func() {
		defer w.wg.Done()
		defer func() {
			m.mu.Lock()
			delete(m.workers, id)
			m.mu.Unlock()
			m.onWorkerDone()
		}()

		// acquire or add torrent
		var tor *torrent.Torrent
		var err error
		m.mu.Lock()
		tor = m.btTorrents[id]
		m.mu.Unlock()
		if tor == nil {
			t.AddLog("info", "添加新种子")
			if strings.HasPrefix(strings.ToLower(t.URL), "magnet:") {
				t.AddLog("info", "从磁力链接添加种子")
				tor, err = m.btClient.AddMagnet(t.URL)
			} else {
				t.AddLog("info", fmt.Sprintf("从文件添加种子: %s", t.URL))
				tor, err = m.btClient.AddTorrentFromFile(t.URL)
			}
			if err != nil {
				t.AddLog("error", fmt.Sprintf("添加种子失败: %v", err))
				m.failTask(id, fmt.Errorf("bt add: %w", err))
				return
			}
			// Apply global trackers if configured
			m.mu.Lock()
			gts := append([]string(nil), m.btTrackers...)
			m.mu.Unlock()
			if len(gts) > 0 {
				// Apply global trackers as a single tier. We don't merge existing due to API constraints here.
				merged := make([]string, 0, len(gts))
				uniq := make(map[string]struct{})
				for _, u := range gts {
					if _, ok := uniq[u]; ok { continue }
					uniq[u] = struct{}{}
					merged = append(merged, u)
				}
				tor.ModifyTrackers([][]string{merged})
				t.AddLog("info", fmt.Sprintf("应用全局 Trackers: %d 条", len(merged)))
			}
			m.mu.Lock()
			m.btTorrents[id] = tor
			m.mu.Unlock()
			t.AddLog("info", "种子添加成功，等待元数据...")
		}

		// wait for metadata
		<-tor.GotInfo()
		info := tor.Info()
		name := info.Name
		// update name/save path
		m.mu.Lock()
		if name != "" {
			t.Name = name
		}
		t.AddLog("info", fmt.Sprintf("种子名称: %s", name))
		t.AddLog("info", fmt.Sprintf("文件数量: %d", len(info.Files)))
		t.AddLog("info", fmt.Sprintf("总大小: %s", formatFileSize(info.TotalLength())))
		// final path target directory under dir
		finalDir := filepath.Join(dir, name)
		if name != "" {
			t.SavePath = finalDir
		}
		m.mu.Unlock()
		// start or resume download
		t.AddLog("info", "开始下载文件...")
		tor.DownloadAll()
		// resume network connections (negative means default/unlimited)
		tor.SetMaxEstablishedConns(-1)

		// progress loop
		lastBytes := int64(0)
		lastTick := time.Now()
		ticker := time.NewTicker(500 * time.Millisecond)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				// Pause: limit connections to 0, keep torrent for quick resume
				tor.SetMaxEstablishedConns(0)
				m.mu.Lock()
				t.Status = StatusPaused
				t.Speed = 0
				m.mu.Unlock()
				m.emitUpdate()
				return
			case <-ticker.C:
				// compute size and downloaded
				total := info.TotalLength()
				completed := tor.BytesCompleted()
				now := time.Now()
				elapsed := now.Sub(lastTick).Seconds()
				var speed int64 = 0
				if elapsed > 0 {
					speed = int64(float64(completed-lastBytes) / elapsed)
				}
				lastBytes = completed
				lastTick = now
				m.mu.Lock()
				t.Size = total
				t.Downloaded = completed
				t.Speed = speed
				if total > 0 && speed > 0 {
					t.ETA = int64(float64(total-completed) / float64(speed))
				} else {
					t.ETA = 0
				}
				m.mu.Unlock()
				m.emitUpdate()
				// finish
				if total > 0 && completed >= total {
					t.AddLog("info", "下载完成")
					m.mu.Lock()
					t.Status = StatusCompleted
					t.Speed = 0
					t.ETA = 0
					m.mu.Unlock()
					m.emitUpdate()
					if !m.btKeepSeeding {
						t.AddLog("info", "停止做种")
						// stop seeding by limiting connections and clearing map reference
						tor.SetMaxEstablishedConns(0)
						m.mu.Lock()
						delete(m.btTorrents, id)
						m.mu.Unlock()
					} else {
						t.AddLog("info", "继续做种...")
					}
					return
				}
			}
		}
	}()
	return nil
}
